package com.c0nflict.server;

import java.lang.Math;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;

public class Table {
	String eol = System.getProperty("line.separator");

	/*
	 * Table status 0 = Uninitialized 1 = Not running / finished / between games
	 * 2 = Running
	 * 3 = Shutdown
	 */
	final static int TS_UNINITIALIZED = 0;
	final static int TS_NOT_RUNNING = 1;
	final static int TS_RUNNING = 2;
	final static int TS_SHUTDOWN = 3;
	public boolean tableIsStarting = false;
	private long javascriptID = 0;
	
	private boolean isRated = true;
	private boolean isQuiet = false;
	public void setQuiet(boolean quiet) { isQuiet = quiet; }
	public boolean isQuiet() { return isQuiet;}
	private int elo_minimum = 0;
	
	public void setEloMinimum(int elo) {
		elo_minimum = elo;
	}
	public int getEloMinimum() {
		return elo_minimum;
	}
	
	public void setRated(boolean rated) {
		isRated = rated;
	}
	
	public boolean isRated() {
		return isRated;
	}
	
	public long getJSID() { return javascriptID; }
	public void setJSID(long id) { javascriptID = id; }
	
    public TableLogger logger = null;
    
	final static int TURN_DURATION = 13; // Each player's turn in seconds

	final static int MAX_PLAYERS = 6; // Number of players on the table
    public GameStats gameStats = null;
    
	private int partialVictory = 1;

	private String tableName = ""; // The name of the table
	private int tableId = 0;
	
	private int tableStatus = 0; // Status of table (see above)
	private int tableWidth = 0; // Table width
	private int tableHeight = 0; // Table height
	private int tableSize = 0; // Width * Height - used to speed up processing

	public int tableTotalElo = 0; // Total ELO of players at table
	public int tableEloCount = 0;
	public int totalDeathElo = 0;
	public int deathEloCount = 0;

	private int firstPlayerId = 0;
	private int currentPlayerId = 0;
	private Player currentPlayer = null;
	private int currentTurn = 1;
	
	private boolean predefinedLayout = false;	// Does this table use a predefined layout
	private String author = null;				// The author of the table (if predefined)
	private String layout = null;				// The table layout (if predefined)
	
	//TODO Put these values in the table
	private double movableAreasPercent = .35;
	private double movableDicePercent = .50;
	
	public double getMovableAreasPercent() { return movableAreasPercent; }
	public double getMovableDicePercent() { return movableDicePercent; }
	
	//TODO Add number of players to db and load into this variable
	private int maxPlayers = MAX_PLAYERS; // Maximum number of players

	private int playerStartingDice = 0; 	// Starting dice for each player
	private int minPlayerAreas = 5; 		// Minimum number of areas assigned to each player (guaruntee)
	private int maxDicePerSquare = 10; 		// Maximum dice per square
	private int maxDiceRoll = 10; 			// Maximum value of each dice rolled
	private double defaultPercentBlocked = .25; // Default percent of the table
												// to be blocked (black) - the
												// rest will be grey :P
	
	public Player[] tableSeats = null; 		// Persistent array of seats at the table
	public Player[] players = null; 		// Players array (initially all pointing to empty seats)
	private Player emptyPlayer = null; 		// Dummy player to own all empty squares
	private Player blockedPlayer = null; 	// Dummy player to own all blocked squares

	public int attackSquare = -1; // Stored attacking square
	public int defendSquare = -1; // stored defending square

	//TODO Move roll and related tasks into a new DiceAttack class
	public int roll[][] = new int[2][(maxDicePerSquare + 1) * 2]; // Stored dice roll
	public TableSquare[] squares = null;
	private String authorName= "";
	public void setAuthorName(String a) {
		authorName = a;
	}
	public String getAuthorName() {
		return authorName;
	}
	public int getId() { return tableId; }

	public String getName() { return tableName; }
	public void setName(String name) { tableName = name; }

	public int getMaxPlayers() { return maxPlayers; }
	public void setMaxPlayers(int players) { maxPlayers = players; }

	public int getFirstPlayerId() {	return firstPlayerId; }
	public void setFirstPlayerId(int id) { gameStats.setFirstPlayerId(id); firstPlayerId = id; setCurrentPlayer(id); }

	public int getCurrentPlayerId() { return currentPlayerId; }
	public void setCurrentPlayer(int id) { 
		currentPlayerId = id;
		currentPlayer = players[currentPlayerId];
	}
	
	public boolean isPredefined() { return predefinedLayout; }
	public String getAuthor() { return author; }
	public void setAuthor(String name) { author = name; }
	
	public void setLayout(String newLayout) {
		layout = newLayout;
		predefinedLayout = true;
		//System.out.println("WESET");
	}
	
	public double getDefaultPercentBlocked() { return defaultPercentBlocked; }
	public void setDefaultPercentBlocked(double defaultBlocked) { defaultPercentBlocked = defaultBlocked; }

	public int getMaxDicePerSquare() { return maxDicePerSquare;	}
	public void setMaxDicePerSquare(int dice) { maxDicePerSquare = dice; }

	public int getMinPlayerAreas() { return minPlayerAreas; }
	public void setMinPlayerAreas(int areas) { minPlayerAreas = areas; }

	public int getWidth() {	return tableWidth; }
	public void setWidth(int width) { tableWidth = width; }

	public int getHeight() { return tableHeight; }
	public void setHeight(int height) {	tableHeight = height; }

	public int getTableSize() {	return tableSize; }
	public void setTableSize() { tableSize = tableWidth * tableHeight; }

	public int getStatus() { return tableStatus; }
	public void setStatus(int status) {	tableStatus = status; }

	public int getCurrentTurn() { return currentTurn; }

	public void stop() { maxPlayers = MAX_PLAYERS; tableStatus = TS_NOT_RUNNING; }
	public void start() { tableStatus = TS_RUNNING; }
	public void shutdown() { isShuttingDown = true;	}

	private boolean isShuttingDown = false;
	public boolean isNotInitialized() {	return (tableStatus == TS_UNINITIALIZED); }
	public boolean isRunning() { return (tableStatus == TS_RUNNING); }
	public boolean isNotRunning() {	return (tableStatus == TS_NOT_RUNNING);	}
	public boolean isShutdown() { return (isShuttingDown); }
	public Player getNobody() { return this.emptyPlayer; }
	
	public void moveToNextPlayer() {
		if (++currentPlayerId >= maxPlayers)
			currentPlayerId = 0;

		currentPlayer = players[currentPlayerId];
		if (currentPlayer == null) {
			System.out.println("ERROR: Bad info for player id "	+ currentPlayerId);
			return;
		}

		if (currentPlayerId == firstPlayerId)
			currentTurn++;

		while (currentPlayer.isNobody()) {
			if (++currentPlayerId >= maxPlayers)
				currentPlayerId = 0;

			if (currentPlayer == null)
				System.out.println("ERROR: Bad info for player id "	+ currentPlayerId);

			currentPlayer = players[currentPlayerId];
			
			if (currentPlayerId == firstPlayerId)
				currentTurn++;
		}
	}
	
	int rating_estimate = 0;
	
	public void clearRating() {
		rating_estimate = 0;
		ratingAverage = 0.0;
		ratingVotes = 0;
	}
	
	public void modifyRating(int rating) {
		if (rating == 1) rating_estimate -= 4;
		else if (rating == 2) rating_estimate -= 1;
		else if (rating == 4) rating_estimate += 1;
		else if (rating == 5) rating_estimate += 3;
		
		ratingAverage = ((ratingAverage * (double)ratingVotes) + rating) / (ratingVotes + 1);
		ratingVotes++;
	}
	
	public int getRatingEstimate() {
		return rating_estimate;
	}
	
	double ratingAverage = 0.0;
	int ratingVotes = 0;
	
	public int getRatingVotes() { return ratingVotes; }

	public double getRatingAverage() { return ratingAverage; }
	
	public void moveAutomaton() {
		new Thread() {
			public void run() { moveAutomatonThread(); }
		}.start();
	}
	
	@SuppressWarnings("unchecked")
	
	public int getRealPlayersLeft() {
		int ret = 0;
		for (int x=0;x<this.getMaxPlayers();x++)
			if (players[x].isPlayer() && !players[x].isAutomaton())
				ret++;
		return ret;
	}
	
	int PERSONALITY_AGGRESSIVE = 3;
	int PERSONALITY_CAUTIOUS = 2;
	int PERSONALITY_RANDOM = 1;
	//t PERSONALITY_TYPE = 3;
	
	
	public void moveAutomatonThread() {
		if (currentPlayer == null || isNotRunning()) return;

		if (currentPlayer.PERSONALITY_TYPE==PERSONALITY_RANDOM) currentPlayer.setPersonality(PERSONALITY_RANDOM);
		
		/* Desired logic
		 * - Loop through squares (DONE)
		 * - Limit number of attacks to 40% of available squares (DONE)
		 * - Attack empty squares first (DONE)
		 * - Check for lowest dice empty square
		 * - Do not attack squares bigger by more than 1 (DONE)
		 * - Try to attack first those who attacked it most
		 * - Attack smaller squares first
		 * - Only attack up/down/left/right to stay connected (DONE)
		 * - Attack stronger players before weaker ones
		 * - If reserves are available, check diagonals
		 */
		
		if (!currentPlayer.isAutomaton())
			return;
			
		Vector<TableSquare> playerSquares = currentPlayer.getSquares();
		int squareCount = playerSquares.size();
		
		int adjCount = 0;
		int tooManyLoops = 0;
		TableSquare dest = null;
		TableSquare square = null;
		int movesToMake = (int)(squareCount*currentPlayer.PERSONALITY_ATTACK_PERCENT)+currentPlayer.PERSONALITY_ATTACK_MOD;
		int moves = 0;
		if (movesToMake!=0)
			moves = (int)tm.R.randomBaseZero(movesToMake);
		else moves=1;
		if (this.getCurrentTurn()<2) {
			moves = squareCount;
			
		}
		if (currentPlayer.getPlusDice()>20) {
			moves+=currentPlayer.getPlusDice()/19;
		}
		//if (1==2) {
 		if ((/*isQuiet() && */getRealPlayersLeft()<=0)/* || (this.getCurrentTurn()>10 && currentPlayer.getAreaCount()<6)*/) {
			//flag (but not now, we need them for display games!)
			Player toDie = currentPlayer;
			try {
				
			logger.logEvent(toDie.getName(),"botflag","");

			tm.tracker.srv.serverPlayerDeath(tm, this, toDie.getOrderId());

			lastTimerTick.setTime((new Date()).getTime()+1000);
			
			//lastTimerTick = resetTime;
			} catch (Exception e) {}
			
			return;
		}
		//}
		
		// Select a random square to start moving from
		int startingSquare = tm.R.randomBaseZero(squareCount);
		int currentSquare = startingSquare;
		
		TableSquare tmp = null;
		
		Vector<TableSquare> attackSquares = new Vector<TableSquare>(4);
		
		//System.out.println(currentPlayer.getName() + ": " + squareCount + " squares = " + moves + " moves");
		if (moves==0 && currentPlayer.getTotalDice()>0) moves=1;
		int suckers=0;
		boolean attackDiagonally = false;
		while (moves > 0 && suckers < 10000) {
			suckers++;
			Player winner = getWinningPlayer();
			if (++currentSquare >= squareCount) currentSquare = 0;
			
			// If we went over all squares without a possible attack, quit for now
			// In the future, start checking diagonals to expand once reserves are available
			if (attackSquares.size() == 0 && currentSquare == startingSquare)
				attackDiagonally = true;;
			
			try {
				if (currentSquare >= playerSquares.size()) {
					//Thread.sleep(2000);
					//currentSquare = playerSquares.size()-1;
					return;
				}
				
				square = playerSquares.get( currentSquare);
			} catch (Exception e) {
				System.out.println(playerSquares.size() + ":  "+ currentSquare);

				break;
			}
			
			tooManyLoops = 0;
			
			//System.out.println("   Move = " + moves + "  currentSquare = " + square.getId());
			
			//System.out.print("moving "+squares[x].getId()+"/"+squares[x].getColor()+"/"+squares[x].getDice()+"/"+toomany);
			int trailMod = 0;
			
			boolean firstMoveDie = true;
			while (square.getDice() > 1 && tooManyLoops < 30) {
				tooManyLoops++;
				Vector<Integer> adj = adjacentSquares(square.getId(), false);
				
				Vector<Integer> adjAttackable = attackableSquares(square.getId());
				
				adjCount = adjAttackable.size();

				dest = null;
				tmp = null;
				attackSquares.clear();
				//boolean thisSquareIsConnected = false;
				// Check if any adjacent squares are empty and create list of possible attacks
				for (int i = 0; i < adjCount; i++) {
					tmp = squares[((Integer)adjAttackable.get(i)).intValue()];
					
					//if (tmp.getOwner() == currentPlayer) {
						//thisSquareIsConnected = true;
					//	continue;
					//}
					
					//check here to see if we have a connectable square
					
					Vector<Integer> adjStep = adjacentSquares(tmp.getId(),false);
					int adjStepCount = adjStep.size();
					for (int is = 0; is < adjStepCount; is++) {
						TableSquare tmpStep= squares[((Integer)adjStep.get(is)).intValue()];
						if (tmpStep.getOwner()==currentPlayer && square.getDice()>=tmp.getDice()-partialVictory*2 - (currentPlayer.PERSONALITY_RISK_IN_ATTACK*(attackDiagonally?1:0))) {  //desire to connect!
							if (!tmpStep.getOwner().isAutomaton() || attackDiagonally)
							  for (int c=0;c<6;c++)
								attackSquares.add(tmp);
						}
					}
					
					boolean UPLR = false;
					for (int at=0;at<adj.size();at++) {
						if (adj.get(at).intValue()==tmp.getId()) {
							UPLR = true;
						}
					}
					
					if (tmp.isEmpty() && UPLR) {
						attackSquares.add(tmp);
					}
					
					
					if ((attackDiagonally || UPLR) && (square.getDice() >= tmp.getDice() - (currentPlayer.PERSONALITY_RISK_IN_ATTACK*(attackDiagonally?1:.1)))) {
						attackSquares.add(tmp);
						// If it's not an automaton, add it again to increase chance of attacking it
						if (!tmp.getOwner().isAutomaton()) {
							attackSquares.add(tmp);
							if (tmp.getOwner()==winner) 
								for (int z=0;z<10;z++)
									attackSquares.add(tmp);
						}
					}
				}
				
				// Randomly select square to attack if no empty squares to occupy
				if (dest == null) {
					// If no available squares to attack, move to next square (break out of while loop)
					if (attackSquares.size() == 0)
						break;
						
					dest = attackSquares.get(tm.R.randomBaseZero(attackSquares.size()));
					//System.out.println("      Random attack square = " + dest.getId());
				}
				
				attackSquare = -1;
				if (dest.getOwner()!=currentPlayer && square.getOwner()==currentPlayer)
				try {
					tm.tracker.wowza(square.getId(), currentPlayer.getSession());
					Thread.sleep(400);
					tm.tracker.wowza(dest.getId(), currentPlayer.getSession());
					Thread.sleep(850);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}

				if (dest.getOwner() == currentPlayer) {
					// we won the attack, set this to be the current square
					square = dest;
					//TooManyLoops =0;
					trailMod++;
				}
				}
			
			// Only count as a move if was able to attack
			if (attackSquares.size() > 0 && firstMoveDie) moves--;
		}

		try { Thread.sleep(400); }
		catch (Exception e) { e.printStackTrace(); }
		
		// End turn using the timer watchdog
		lastTimerTick = resetTime;
	}

    public Date lastTimerTick = new Date();
    public Date resetTime = new Date();
	public void resetClock() { lastTimerTick = new Date(); }

	public void killPlayer(Player user) {
		System.out.println("killPlayer: Killing player " + user.getName());
		if (user == null)
			return;

		reassignSquares(user, emptyPlayer);
	}

	public int calcLargestArea(int userint) {
		Player user = players[userint];
		int ret = 0;
		int squareid = 0;
		HashSet<Integer> followed = new HashSet<Integer>();
		int largest = -1;

		for (TableSquare square : user.getSquares()) {
			squareid = square.getId();

			if (!followed.contains(squareid)) {
				ret++;
				followed.add(squareid);
				ret += addNeighbors(squareid, followed, user);
				if (ret > largest)
					largest = ret;
				ret = 0;			}
		}

		return largest;
	}

	private int addNeighbors(int x, HashSet<Integer> followed, Player user) {
		int z;
		int ret = 0;

		//TODO Optimize calculations
		// Left
		z = x - 1;
		if (z >= 0 && z < tableSize && x % tableWidth != 0)
			if (squares[z].getOwner() == user && !followed.contains(z)) {
				ret++;
				followed.add(z);
				ret += addNeighbors(z, followed, user);
			}

		// Right
		z = x + 1;
		if (z >= 0 && z < tableSize && x % tableWidth != tableWidth - 1)
			if (squares[z].getOwner() == user && !followed.contains(z)) {
				ret++;
				followed.add(z);
				ret += addNeighbors(z, followed, user);
			}

		// Up
		z = x - tableWidth;
		if (z >= 0 && z < tableSize)
			if (squares[z].getOwner() == user && !followed.contains(z)) {
				ret++;
				followed.add(z);
				ret += addNeighbors(z, followed, user);
			}

		// Down
		z = x + tableWidth;
		if (z >= 0 && z < tableSize)
			if (squares[z].getOwner() == user && !followed.contains(z)) {
				ret++;
				followed.add(z);
				ret += addNeighbors(z, followed, user);
			}

		// Down/left
		z = x + tableWidth - 1;
		if (z >= 0 && z < tableSize && x % tableWidth == 0)
			if (squares[z].getOwner() == user && !followed.contains(z)) {
				ret++;
				followed.add(z);
				ret += addNeighbors(z, followed, user);
			}

		// Up/right
		z = x - tableWidth + 1;
		if (z >= 0 && z < tableSize && x % tableWidth == tableWidth - 1)
			if (squares[z].getOwner() == user && !followed.contains(z)) {
				ret++;
				followed.add(z);
				ret += addNeighbors(z, followed, user);
			}

		return ret;
	}

	/*
	 * Return codes 1 = First square depressed 2 = First square un-depressed 3 =
	 * Second square depressed in attack
	 */
	public int squareDepressed(int x, Player user) {
		// System.out.println(user.getName() + " depressed square " + x);

		// If not player's turn, do nothing
		if (user.getOrderId() != currentPlayerId)
			return 0;

		// If the user owns the square
		if (squares[x].getOwner() == user) {
			// Do nothing if trying to click on a square with 0 or 1 dice
			if (squares[x].getDice() <= 1)
				return 0;
			// If first click, select square
			else if (attackSquare == -1) {
				attackSquare = x;
				return 1;
			}
			// If second click on same square, un-select it
			else if (attackSquare == x) {
				attackSquare = -1;
				return 2;
			}
		}
		// If in attack mode and trying to attack a non-blocked square
		else if (attackSquare != -1 && !squares[x].isBlocked()) {
			if (( (attackSquare % tableWidth != tableWidth - 1 && attackSquare % tableWidth != 0) 
					&& (Math.abs(x - attackSquare) < 2
							|| Math.abs(x - (attackSquare - tableWidth)) < 2 
							|| Math.abs(x - (attackSquare + tableWidth)) < 2)
				)
				||
				( // Beginning of board
					attackSquare % tableWidth == 0 && 
					(x == attackSquare - 1 // Left
						|| x == attackSquare + 1 // Right
						|| x == attackSquare - tableWidth // Up
						|| x == attackSquare + tableWidth // Down
						|| x == attackSquare - tableWidth + 1 // Up/right diagonal
						|| x == attackSquare + tableWidth - 1 // Down/left diagonal
						|| x == attackSquare + tableWidth + 1 // Down/right diagonal
						|| x == attackSquare + 2 * tableWidth - 1 // Down/left diagonal
					)
				) 
				||
				( // End of board
					attackSquare % tableWidth == tableWidth - 1 &&
					(x == attackSquare - 1 // Left
						|| x == attackSquare + 1 // Right
						|| x == attackSquare - tableWidth // Up
						|| x == attackSquare + tableWidth // Down
						|| x == attackSquare - tableWidth + 1 // Up/right diagonal
						|| x == attackSquare + tableWidth - 1 // Down/left diagonal
						|| x == attackSquare - tableWidth - 1 // Up/left diagonal
						|| x == attackSquare - 2 * tableWidth + 1 // Up/right diagonal
					)
				)) {
				// System.out.println(" square " + attackSquare + " attacking square " + x);
				defendSquare = x;
				//FIXME (if a depressed offturn square just got taken clear it from the player
				//if (defendSquare.getOwner().)
				if (squares[defendSquare].getOwner().fromSquare==squares[defendSquare])
					squares[defendSquare].getOwner().fromSquare=null;
					// Roll attack
				int attacker = 0;
				int defender = 0;

				for (int f = 0; f < (maxDicePerSquare + 1) * 2; f++) {
					roll[0][f] = 0;
					roll[1][f] = 0;
				}
				/*
				 //FIXME WHAT THE FUCK IS THIS DOING HERE
				 
				for (int f = 0; f < squares[attackSquare].getDice(); f++) {
					roll[0][f] = tm.R.randomBaseOne(maxDiceRoll);
					attacker += roll[0][f];
				}

				for (int f = 0; f < squares[defendSquare].getDice(); f++) {
					roll[1][f] = tm.R.randomBaseOne(maxDiceRoll);
					defender += roll[1][f];
				}
				*/
				//System.out.println("roll!:"+attacker+" "+defender);
				//TODO Add "invincible" mode check
				//TODO Fix when losing to an empty square
				if (partialVictory == 1	&& (!squares[defendSquare].isEmpty() || squares[defendSquare].getDice() > 0)) {
					double attackMod = 
						1 + (squares[attackSquare].getDice() - squares[defendSquare].getDice()) 
						* .5 / (double) maxDicePerSquare;

					int attacksWon = 0;
					int attacksLost = 0;
					int disp_int = 0;
					squares[attackSquare].addDice(1);

					while (attacksLost < squares[attackSquare].getDice() - 1 && 
							attacksWon < squares[defendSquare].getDice()) {
						if (attacksLost < squares[attackSquare].getDice() - 1)
							roll[0][disp_int] = tm.R.randomBaseOne(attackMod * maxDiceRoll);
						else
							roll[0][disp_int] = 0;

						if (attacksWon < squares[defendSquare].getDice())
							roll[1][disp_int] = tm.R.randomBaseOne(maxDiceRoll);
						else
							roll[1][disp_int] = 0;

						if (roll[0][disp_int] > roll[1][disp_int])
							attacksWon++;
						else
							attacksLost++;

						disp_int++;
					}

					// Defender
					squares[defendSquare].addDice(-attacksWon);

					// Attacker
					squares[attackSquare].addDice(-attacksLost);
					//System.out.println("SQDICELEFT:" + squares[defendSquare].getDice());
					if (squares[defendSquare].getDice() == 0) {
						squares[defendSquare].setOwner(user);
						squares[defendSquare].addDice(squares[attackSquare].getDice() - 1);
						squares[attackSquare].setDice(1);
					}
				} else {
					for (int f = 0; f < squares[attackSquare].getDice(); f++) {
						roll[0][f] = tm.R.randomBaseOne(maxDiceRoll);
						attacker += roll[0][f];
					}

					for (int f = 0; f < squares[defendSquare].getDice(); f++) {
						roll[1][f] =  tm.R.randomBaseOne(maxDiceRoll);
						defender += roll[1][f];
					}

					// System.out.println("roll!:"+attacker+" "+defender);
					//TODO Add "invincible" mode check
					if (attacker > defender) {
						squares[defendSquare].setDice(squares[attackSquare].getDice() - 1);
						squares[attackSquare].setDice(1);
						squares[defendSquare].setOwner(user);
					} else {
						squares[attackSquare].setDice(1);
					}
				}

				return 3;
			}
		}

		return 0;
	}

	public Vector<Integer> adjacentSquares(int x, boolean getBlocked) {
		// Do not return areas outside table boundaries
		//TODO Do not return blocked squares
		
		Vector<Integer> adjacents = new Vector<Integer>(4);
		
		int xup = x - tableWidth;
		int xdown = x + tableWidth;
		
		if (xup >= 0 && (getBlocked || !squares[xup].isBlocked())) adjacents.add(xup); // Up
		if (xdown < tableSize && (getBlocked || !squares[xdown].isBlocked())) adjacents.add(xdown); // Down
		
		// Right
		if (x % tableWidth == tableWidth - 1) { // Right edge
			if (xup + 1 >= 0 && (getBlocked || !squares[xup + 1].isBlocked())) adjacents.add(xup + 1);
		}
		else // Everywhere else
			if (x + 1 < tableSize && (getBlocked || !squares[x + 1].isBlocked())) adjacents.add(x + 1);
		
		// Left	
		if (x % tableWidth == 0) { // Left edge
			if (xdown - 1 < tableSize && (getBlocked || !squares[xdown - 1].isBlocked())) adjacents.add(xdown - 1);
		}
		else // Everywhere else
			if (x - 1 >= 0 && (getBlocked || !squares[x - 1].isBlocked())) adjacents.add(x - 1);

		return adjacents;
	}
	public Vector<Integer> attackableSquares(int x) {
		// Do not return areas outside table boundaries
		//TODO Do not return blocked squares
		boolean getBlocked = false;
		Vector<Integer> adjacents = new Vector<Integer>(4);
		
		int xup = x - tableWidth;
		int xdown = x + tableWidth;
		
		if (xup >= 0 && (getBlocked || !squares[xup].isBlocked())) adjacents.add(xup); // Up
		if (xdown < tableSize && (getBlocked || !squares[xdown].isBlocked())) adjacents.add(xdown); // Down

		
		//FIXME this doesnt test for edges, and returns incorrect data ;P
		if (x%tableWidth!=tableWidth-1) {
			if (xup+1 >= 0 && (getBlocked || !squares[xup+1].isBlocked())) adjacents.add(xup+1); // Up
			if (xdown+1 < tableSize && (getBlocked || !squares[xdown+1].isBlocked())) adjacents.add(xdown+1); // Down
		}
		if (x%tableWidth!=0) {
			if (xup-1 >= 0 && (getBlocked || !squares[xup-1].isBlocked())) adjacents.add(xup-1); // Up
			if (xdown-1 < tableSize && (getBlocked || !squares[xdown-1].isBlocked())) adjacents.add(xdown-1); // Down
		}
		// Right
		if (x % tableWidth == tableWidth - 1) { // Right edge
			if (xup + 1 >= 0 && (getBlocked || !squares[xup + 1].isBlocked())) adjacents.add(xup + 1);
		}
		else // Everywhere else
			if (x + 1 < tableSize && (getBlocked || !squares[x + 1].isBlocked())) adjacents.add(x + 1);
		
		// Left	
		if (x % tableWidth == 0) { // Left edge
			if (xdown - 1 < tableSize && (getBlocked || !squares[xdown - 1].isBlocked())) adjacents.add(xdown - 1);
		}
		else // Everywhere else
			if (x - 1 >= 0 && (getBlocked || !squares[x - 1].isBlocked())) adjacents.add(x - 1);

		return adjacents;
	}

	public void ensureContiguous() {	
		for (int x = 0; x < tableSize; x++) {
			if (squares[x].isBlocked())
				continue;
			
			Vector<Integer> adj = adjacentSquares(x, true);
			int adjCount = adj.size();
			
			HashMap<TableSquare, Integer> alreadyScrewed = new HashMap<TableSquare,Integer>();
			int nonblack = 0;
			
			for (int z = 0; z < adj.size(); z++)
				if (!squares[((Integer) adj.get(z)).intValue()].isBlocked())
					nonblack++;
			
			Collection<TableSquare> screwdList = alreadyScrewed.keySet();
			int howbad = 1;
			
			try {
				howbad = alreadyScrewed.get(squares[x]).intValue(); 
			} catch (Exception e) {
				//e.printStackTrace();
				//System.out.println("square:"+x);
				//howbad=;
			}
			
			int howmany = 0;
			if (nonblack <= howbad) {
				howbad++;

				int randomSquare = ((Integer)adj.get(tm.R.randomBaseZero(adjCount))).intValue();
				for (Integer i : adj) {
					if (screwdList.contains(squares[i])) {
						howmany = alreadyScrewed.get(squares[i]) + howbad; //+1 here makes it contiguous but.. not alot of black.
						alreadyScrewed.remove(squares[i]);
						alreadyScrewed.put(squares[i], howmany);
					} else
						alreadyScrewed.put(squares[i], howbad);
				}
				
				int toomany = 0;
				if (howbad > howmany) howmany = howbad;
				
				while (howmany > 1) {
					howmany--;
					while (!squares[randomSquare].isBlocked())
					{
						toomany++;
						randomSquare = ((Integer)adj.get(tm.R.randomBaseZero(adjCount))).intValue();
						if (toomany > 20) { randomSquare = -1; break; }
					}
					
					if (randomSquare != -1)
						squares[randomSquare].setOwner(emptyPlayer);
				}
			}
		}
	}

	public void clearSeats() {
		for (int x = 0; x < maxPlayers; x++)
			players[x] = tableSeats[x];
		
		attackSquare = -1;
		defendSquare = -1;
	}

	public void reassignSquares(Player fromPlayer, Player toPlayer) {
		for (int i = 0; i < tableSize; i++)
			if (squares[i].getOwner() == fromPlayer)
				squares[i].setOwner(toPlayer);
	}
	
	private TableManager tm = null;
	
	public void reset(TableManager tm) {
		tableIsStarting = false;
		this.tm = tm;
		setStatus(TS_UNINITIALIZED);
		
		maxPlayers = MAX_PLAYERS;
		currentTurn = 1;

		int totalPlaced = 0;
		int totalAreas = 0;
		
		int randomPlayer = 0;
		int randomSquare = 0;

		emptyPlayer.resetAreas();
		blockedPlayer.resetAreas();

		// Set all squares as empty and set dice to 0
		for (int x = 0; x < tableSize; x++) {
			squares[x].setId(x);
			squares[x].setOwner(emptyPlayer);
			squares[x].setDice(0);
		}

		tableTotalElo = 0;
		tableEloCount = 0;
		totalDeathElo = 0;
		deathEloCount = 0;
		
		attackSquare = -1;
		defendSquare = -1;
		
		for (int x = 0; x < maxPlayers; x++) {
			if (players[x] == null)
				players[x] = tableSeats[x];
			
			if (players[x] == tableSeats[x])
				players[x].setNobody(x);
			players[x].setTable(this);
			players[x].resetAreas();
			players[x].setPlusDice(0);
			if (players[x]!=tableSeats[x])
				players[x].setRequestStart(false);
			players[x].unIdle();
			if (players[x].isPlayer()) {
				tableTotalElo += players[x].getELO();
				tableEloCount++;
			}
		}
		
		firstPlayerId = getPreviousPlayerId(tm.R.randomBaseZero(maxPlayers));
		if (firstPlayerId == -1)
			firstPlayerId = 0;
		
		currentPlayerId = firstPlayerId;
		logger.logEvent(players[currentPlayerId].getName(), "gamestart", "");
		
		// Use predefined layout if specified
		if (predefinedLayout && layout!=null)
		{
			// Extract square definitions from layout
			String[] squareDefs = layout.split(",");
			int squareCount = squareDefs.length;
			//System.out.println("SQDF"+squareDefs);
			for (int i = 0; i < squareCount; i++)
				if ("0".equals(squareDefs[i]))
					squares[i].setOwner(blockedPlayer);
		}
		
		// Randomly assign players areas
		while (minPlayerAreas * maxPlayers > totalAreas) {
			randomSquare = tm.R.randomBaseZero(tableSize);
			randomPlayer = tm.R.randomBaseZero(maxPlayers);

			if (squares[randomSquare].isEmpty()) {
				while (players[randomPlayer].getAreaCount() >= minPlayerAreas)
					randomPlayer = tm.R.randomBaseZero(maxPlayers);
				squares[randomSquare].setOwner(players[randomPlayer]);
				squares[randomSquare].setDice(1);

				totalAreas++;
				totalPlaced++;
			}
		}

		// System.out.println("Table: Areas set");
		int breakout4=0;
		while (playerStartingDice * maxPlayers > totalPlaced) {
			breakout4++;
			if (breakout4>20000) return;
			randomPlayer = tm.R.randomBaseZero(maxPlayers);
			int breakout3=0;
			while (players[randomPlayer].getTotalDice() >= playerStartingDice) {
				breakout3++;
				if (breakout3>200000) break;
				randomPlayer = tm.R.randomBaseZero(maxPlayers);
			}

			randomSquare = tm.R.randomBaseZero(tableSize);
			int breakout2=0;
			while (squares[randomSquare].getOwner() != players[randomPlayer]
					|| squares[randomSquare].getDice() + 1 == maxDicePerSquare) {
				breakout2++;
				if (breakout2>20000) break;
				randomSquare = tm.R.randomBaseZero(tableSize);
			}

			squares[randomSquare].addDice(1);

			totalPlaced++;
		}

		// System.out.println("Table: Dice set");
		// Randomly create blocked areas
		if (!predefinedLayout)
		{
			int totalblack = 0;
			int breakout6=0;
			while (tableSize * getDefaultPercentBlocked() > totalblack) {
				breakout6++;
				if (breakout6>20000) break;
				randomSquare = tm.R.randomBaseZero(tableSize);
				if (squares[randomSquare].isEmpty()) {
					totalblack++;
					squares[randomSquare].setOwner(blockedPlayer);
				}
			}
		}
		
		// System.out.println("MAP: BLACKS SET");
		if (!predefinedLayout)
			ensureContiguous();
		removeUserSeats();
		// System.out.println("SEMI CONTIGUOUS DONE.");

		gameStats = new GameStats(maxPlayers,tm);
		gameStats.setTotalPlayers(this.getRealPlayersLeft());
		gameStats.setTableId(tableId);
		gameStats.setFirstPlayerId(firstPlayerId);
		setFirstPlayerId(firstPlayerId);
		for (int x = 0; x < maxPlayers; x++) {
			gameStats.setPlayerDbId(x, players[x].getDbId());
			gameStats.setPlayerELO(x, players[x].getELO());
			gameStats.setPlayerELOChange(x, 0);
			gameStats.setPlayerFinishPlace(x, 0);
			gameStats.setPlayerDeathTime(x, new Date());
		}
		
		logger.logEvent(this.getName(), "newmap", this.getParams()+","+getColors() + "," + getDiceCounts());

		start();
		moveAutomaton();
	}

	public Player getWinningPlayer() {
		try {
			Player winner = players[getPreviousPlayerId(0)];
			for (int x = 0; x < maxPlayers; x++) {
				if (players[x].getTotalDice() > winner.getTotalDice())
					winner = players[x];
			}
			
			return winner;
		} catch (Exception e) { 
			return null; 
		}
	}
	
	public int getPreviousPlayerId(int id) {
		int repitions = 0;
		int newid = id - 1;
		
		//System.out.println("TABLE MAX:" + maxPlayers);
		
		if (newid < 0) 
			newid = maxPlayers - 1;
		
		while (!players[newid].isPlayer()) {
			repitions++;
			
			if (repitions > 20)
				return -1;
			
			newid--;
			
			if (newid < 0) 
				newid = maxPlayers - 1;
		}
		
		return newid;
	}
	
	public void removeUserSeats() {
		Vector<TableSquare> deadSquares = new Vector<TableSquare>();
	    
		int newid = getPreviousPlayerId(firstPlayerId);
		if (newid == -1) return;
		
		int numPlayers = 0;
		for (int x = 0; x < maxPlayers; x++) 
			if (players[x].isPlayer())
				numPlayers++;
			else
				deadSquares.addAll(players[x].getSquares());
		
		//this fucks everything up. 
		//maxPlayers = numPlayers;
		
		if (deadSquares.size() % numPlayers != 0) {
			//we cant evenly distribute squares -- go screw off
	
			for (int dc = 0; dc < deadSquares.size(); dc++) {
						deadSquares.get(dc).setOwner(this.emptyPlayer);
						deadSquares.get(dc).setDice(0);
				}
				
			

		} else {
			
			while (deadSquares.size() > 0) {
				int largest = -1;
				TableSquare largestSquare = null;
				
				for (int dc = 0; dc < deadSquares.size(); dc++) {
					if (deadSquares.get(dc).getDice() > largest) {
						largest = deadSquares.get(dc).getDice();
						largestSquare = deadSquares.get(dc);
					}
				}
				
				deadSquares.remove(largestSquare);
				largest = -1;
				largestSquare.setOwner(players[newid]);
				newid = getPreviousPlayerId(newid);
			}
		}
	}
	
	
	public Table(Table t) {
		this(t.getName(),t.getWidth(),t.getHeight(),t.playerStartingDice,t.defaultPercentBlocked,t.getMinPlayerAreas(),t.getMaxDicePerSquare(),t.tableId,t.movableAreasPercent,t.movableDicePercent,t.partialVictory,t.layout,t.author,t.maxDiceRoll);
	}
	
	public String getParams() {
		return tableWidth + ":" + tableHeight + ":" + authorName + ":" + maxDicePerSquare + ":" +
			partialVictory + ":" + movableAreasPercent + ":" + movableDicePercent;
	}
	
	public Table(String name, int width, int height, int dice,
			double percentBlocked, int minAreas, int maxDice, int table_id, double mland, double mdice, int pvic,
			String layout, String author, int maxdiceroll) {
		this(name,width,height,dice,percentBlocked,minAreas,maxDice,table_id,mland,mdice,pvic);
		if (layout != null && layout.length() > 0) {
			setLayout(layout);
			setAuthor(author);
		}
		
		if (maxdiceroll > 5)
			maxDiceRoll = maxdiceroll;
	}
	
	public Table(String name, int width, int height, int dice,
			double percentBlocked, int minAreas, int maxDice, int table_id, double mland, double mdice, int pvic) {
		this(width, height, dice);
		tableId = table_id;
		movableAreasPercent = mland;
		movableDicePercent = mdice;
		partialVictory = pvic;
		defaultPercentBlocked = percentBlocked;
		maxDicePerSquare = maxDice;
		minPlayerAreas = minAreas;
		setName(name);
		//logger = new TableLogger(this);
	}
	
    public void startLogger() {
		logger = new TableLogger(this);
    }
    
	public Table(int width, int height, int dice) {
		players = new Player[maxPlayers];
		tableSeats = new Player[maxPlayers];

		for (int x = 0; x < maxPlayers; x++) {
			tableSeats[x] = new Player("Empty Seat",null);
			tableSeats[x].setNobody(x);
			players[x] = tableSeats[x];
		}

		tableWidth = width;
		tableHeight = height;
		tableSize = tableWidth * tableHeight;

		playerStartingDice = dice;

		emptyPlayer = new Player("Nobody",null);
		emptyPlayer.setEmpty();

		blockedPlayer = new Player("Blocked",null);
		blockedPlayer.setBlocked();

		// Create squares as empty and set dice to 0
		squares = new TableSquare[tableSize];
		for (int x = 0; x < tableSize; x++)
			squares[x] = new TableSquare(emptyPlayer, x);
	}

	// Generate an XML representation of the map
	public String toXml() {
		StringBuffer buf = new StringBuffer("<dicequest_table id=" + tableId + ">" + eol);

		int i = 0;

		// Process parameters
		
		//buf.append(" <totalDice>" + totalDice + "</totalDice>" + eol);

		// First player
		buf.append(" <firstPlayerId>" + firstPlayerId + "</firstPlayerId>" + eol);

		// Current player
		buf.append("  <currentPlayer>" + currentPlayer + "</currentPlayer>"	+ eol);

		// Current turn number
		buf.append("  <currentTurn>" + currentTurn + "</currentTurn>" + eol);

		// Width and height
		buf.append("  <width>" + tableWidth + "</width>" + eol);
		buf.append("  <height>" + tableHeight + "</height>" + eol);

		// Process players
		for (i = 0; i < maxPlayers; i++)
			buf.append(players[i].toXml());

		// Process Squares
		for (i = 0; i < tableSize; i++)
			buf.append(squares[i].toXml());

		buf.append("</dicequest_table>");

		return buf.toString();
	}

	public String getColors() {
		StringBuffer buf = new StringBuffer("[");

		for (int i = 0; i < tableSize; i++) {
			if (i > 0)
				buf.append(",");

			buf.append(squares[i].getColor());
		}

		buf.append("]");

		return buf.toString();
	}

	// Return a string containing all dice counts
	public String getDiceCounts() {
		StringBuffer buf = new StringBuffer("[");

		for (int i = 0; i < tableSize; i++) {
			if (i > 0)
				buf.append(",");

			buf.append(squares[i].getDice());
		}

		buf.append("]");

		return buf.toString();
	}
	public void setTableManager(TableManager tm) {
		this.tm = tm;
	}
	public TableManager getTableManager() {
		return tm;
	}

}
